home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 26 / Cream of the Crop 26.iso / program / nasm095s.zip / OUTOBJ.C < prev    next >
C/C++ Source or Header  |  1997-07-27  |  35KB  |  1,295 lines

  1. /* outobj.c    output routines for the Netwide Assembler to produce
  2.  *        Microsoft 16-bit .OBJ object files
  3.  *
  4.  * The Netwide Assembler is copyright (C) 1996 Simon Tatham and
  5.  * Julian Hall. All rights reserved. The software is
  6.  * redistributable under the licence given in the file "Licence"
  7.  * distributed in the NASM archive.
  8.  */
  9.  
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <ctype.h>
  14.  
  15. #include "nasm.h"
  16. #include "nasmlib.h"
  17. #include "outform.h"
  18.  
  19. #ifdef OF_OBJ
  20.  
  21. static char obj_infile[FILENAME_MAX];
  22. static int obj_uppercase;
  23.  
  24. static efunc error;
  25. static ldfunc deflabel;
  26. static FILE *ofp;
  27. static long first_seg;
  28. static int any_segs;
  29.  
  30. #define LEDATA_MAX 1024               /* maximum size of LEDATA record */
  31. #define RECORD_MAX 1024               /* maximum size of _any_ record */
  32. #define GROUP_MAX 256               /* we won't _realistically_ have more
  33.                     * than this many segs in a group */
  34. #define EXT_BLKSIZ 256               /* block size for externals list */
  35.  
  36. static unsigned char record[RECORD_MAX], *recptr;
  37.  
  38. static struct Public {
  39.     struct Public *next;
  40.     char *name;
  41.     long offset;
  42.     long segment;               /* only if it's far-absolute */
  43. } *fpubhead, **fpubtail;
  44.  
  45. static struct External {
  46.     struct External *next;
  47.     char *name;
  48.     long commonsize;
  49. } *exthead, **exttail;
  50.  
  51. static int externals;
  52.  
  53. static struct ExtBack {
  54.     struct ExtBack *next;
  55.     int index[EXT_BLKSIZ];
  56. } *ebhead, **ebtail;
  57.  
  58. static struct Segment {
  59.     struct Segment *next;
  60.     long index;                   /* the NASM segment id */
  61.     long obj_index;               /* the OBJ-file segment index */
  62.     struct Group *grp;               /* the group it belongs to */
  63.     long currentpos;
  64.     long align;                   /* can be SEG_ABS + absolute addr */
  65.     enum {
  66.     CMB_PRIVATE = 0,
  67.     CMB_PUBLIC = 2,
  68.     CMB_STACK = 5,
  69.     CMB_COMMON = 6
  70.     } combine;
  71.     long use32;                   /* is this segment 32-bit? */
  72.     struct Public *pubhead, **pubtail;
  73.     char *name;
  74.     char *segclass, *overlay;           /* `class' is a C++ keyword :-) */
  75. } *seghead, **segtail, *obj_seg_needs_update;
  76.  
  77. static struct Group {
  78.     struct Group *next;
  79.     char *name;
  80.     long index;                   /* NASM segment id */
  81.     long obj_index;               /* OBJ-file group index */
  82.     long nentries;               /* number of elements... */
  83.     long nindices;               /* ...and number of index elts... */
  84.     union {
  85.     long index;
  86.     char *name;
  87.     } segs[GROUP_MAX];               /* ...in this */
  88. } *grphead, **grptail, *obj_grp_needs_update, *defgrp;
  89.  
  90. static struct ObjData {
  91.     struct ObjData *next;
  92.     int nonempty;
  93.     struct Segment *seg;
  94.     long startpos;
  95.     int letype, ftype;
  96.     unsigned char ledata[LEDATA_MAX], *lptr;
  97.     unsigned char fixupp[RECORD_MAX], *fptr;
  98. } *datahead, *datacurr, **datatail;
  99.  
  100. static long obj_entry_seg, obj_entry_ofs;
  101.  
  102. static int os2;
  103.  
  104. enum RecordID {                   /* record ID codes */
  105.  
  106.     THEADR = 0x80,               /* module header */
  107.     COMENT = 0x88,               /* comment record */
  108.  
  109.     LNAMES = 0x96,               /* list of names */
  110.  
  111.     SEGDEF = 0x98,               /* segment definition */
  112.     GRPDEF = 0x9A,               /* group definition */
  113.     EXTDEF = 0x8C,               /* external definition */
  114.     PUBDEF = 0x90,               /* public definition */
  115.     COMDEF = 0xB0,               /* common definition */
  116.  
  117.     LEDATA = 0xA0,               /* logical enumerated data */
  118.     FIXUPP = 0x9C,               /* fixups (relocations) */
  119.  
  120.     MODEND = 0x8A               /* module end */
  121. };
  122.  
  123. extern struct ofmt of_obj;
  124.  
  125. static long obj_ledata_space(struct Segment *);
  126. static int obj_fixup_free(struct Segment *);
  127. static void obj_ledata_new(struct Segment *);
  128. static void obj_ledata_commit(void);
  129. static void obj_write_fixup (struct ObjData *, int, int, long, long, long);
  130. static long obj_segment (char *, int, int *);
  131. static void obj_write_file(void);
  132. static unsigned char *obj_write_data(unsigned char *, unsigned char *, int);
  133. static unsigned char *obj_write_byte(unsigned char *, int);
  134. static unsigned char *obj_write_word(unsigned char *, int);
  135. static unsigned char *obj_write_dword(unsigned char *, long);
  136. static unsigned char *obj_write_rword(unsigned char *, int);
  137. static unsigned char *obj_write_name(unsigned char *, char *);
  138. static unsigned char *obj_write_index(unsigned char *, int);
  139. static unsigned char *obj_write_value(unsigned char *, unsigned long);
  140. static void obj_record(int, unsigned char *, unsigned char *);
  141. static int obj_directive (char *, char *, int);
  142.  
  143. static void obj_init (FILE *fp, efunc errfunc, ldfunc ldef) {
  144.     ofp = fp;
  145.     error = errfunc;
  146.     deflabel = ldef;
  147.     first_seg = seg_alloc();
  148.     any_segs = FALSE;
  149.     fpubhead = NULL;
  150.     fpubtail = &fpubhead;
  151.     exthead = NULL;
  152.     exttail = &exthead;
  153.     externals = 0;
  154.     ebhead = NULL;
  155.     ebtail = &ebhead;
  156.     seghead = obj_seg_needs_update = NULL;
  157.     segtail = &seghead;
  158.     grphead = obj_grp_needs_update = NULL;
  159.     grptail = &grphead;
  160.     datahead = datacurr = NULL;
  161.     datatail = &datahead;
  162.     obj_entry_seg = NO_SEG;
  163.     obj_uppercase = FALSE;
  164.  
  165.     if (os2) {
  166.     obj_directive ("group", "FLAT", 1);
  167.     defgrp = grphead;
  168.     } else
  169.     defgrp = NULL;
  170. }
  171.  
  172. static void dos_init (FILE *fp, efunc errfunc, ldfunc ldef) {
  173.     os2 = FALSE;
  174.     obj_init (fp, errfunc, ldef);
  175. }
  176.  
  177. static void os2_init (FILE *fp, efunc errfunc, ldfunc ldef) {
  178.     os2 = TRUE;
  179.     obj_init (fp, errfunc, ldef);
  180. }
  181.  
  182. static void obj_cleanup (void) {
  183.     obj_write_file();
  184.     fclose (ofp);
  185.     while (seghead) {
  186.     struct Segment *segtmp = seghead;
  187.     seghead = seghead->next;
  188.     while (segtmp->pubhead) {
  189.         struct Public *pubtmp = segtmp->pubhead;
  190.         segtmp->pubhead = pubtmp->next;
  191.         nasm_free (pubtmp);
  192.     }
  193.     nasm_free (segtmp);
  194.     }
  195.     while (fpubhead) {
  196.     struct Public *pubtmp = fpubhead;
  197.     fpubhead = fpubhead->next;
  198.     nasm_free (pubtmp);
  199.     }
  200.     while (exthead) {
  201.     struct External *exttmp = exthead;
  202.     exthead = exthead->next;
  203.     nasm_free (exttmp);
  204.     }
  205.     while (ebhead) {
  206.     struct ExtBack *ebtmp = ebhead;
  207.     ebhead = ebhead->next;
  208.     nasm_free (ebtmp);
  209.     }
  210.     while (grphead) {
  211.     struct Group *grptmp = grphead;
  212.     grphead = grphead->next;
  213.     nasm_free (grptmp);
  214.     }
  215.     while (datahead) {
  216.     struct ObjData *datatmp = datahead;
  217.     datahead = datahead->next;
  218.     nasm_free (datatmp);
  219.     }
  220. }
  221.  
  222. static void obj_deflabel (char *name, long segment,
  223.               long offset, int is_global) {
  224.     /*
  225.      * We have three cases:
  226.      *
  227.      * (i) `segment' is a segment-base. If so, set the name field
  228.      * for the segment or group structure it refers to, and then
  229.      * return.
  230.      *
  231.      * (ii) `segment' is one of our segments, or a SEG_ABS segment.
  232.      * Save the label position for later output of a PUBDEF record.
  233.      * (Or a MODPUB, if we work out how.)
  234.      *
  235.      * (iii) `segment' is not one of our segments. Save the label
  236.      * position for later output of an EXTDEF, and also store a
  237.      * back-reference so that we can map later references to this
  238.      * segment number to the external index.
  239.      */
  240.     struct External *ext;
  241.     struct ExtBack *eb;
  242.     struct Segment *seg;
  243.     int i;
  244.  
  245.     /*
  246.      * First check for the double-period, signifying something
  247.      * unusual.
  248.      */
  249.     if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
  250.     if (!strcmp(name, "..start")) {
  251.         obj_entry_seg = segment;
  252.         obj_entry_ofs = offset;
  253.         return;
  254.     }
  255.     error (ERR_NONFATAL, "unrecognised special symbol `%s'", name);
  256.     }
  257.  
  258.     /*
  259.      * Case (i):
  260.      */
  261.     if (obj_seg_needs_update) {
  262.     obj_seg_needs_update->name = name;
  263.     return;
  264.     } else if (obj_grp_needs_update) {
  265.     obj_grp_needs_update->name = name;
  266.     return;
  267.     }
  268.     if (segment < SEG_ABS && segment != NO_SEG && segment % 2)
  269.     return;
  270.  
  271.     if (segment >= SEG_ABS || segment == NO_SEG) {
  272.     /*
  273.      * SEG_ABS subcase of (ii).
  274.      */
  275.     if (is_global) {
  276.         struct Public *pub;
  277.  
  278.         pub = *fpubtail = nasm_malloc(sizeof(*pub));
  279.         fpubtail = &pub->next;
  280.         pub->next = NULL;
  281.         pub->name = name;
  282.         pub->offset = offset;
  283.         pub->segment = (segment == NO_SEG ? 0 : segment & ~SEG_ABS);
  284.     }
  285.     return;
  286.     }
  287.  
  288.     /*
  289.      * If `any_segs' is still FALSE, we might need to define a
  290.      * default segment, if they're trying to declare a label in
  291.      * `first_seg'.
  292.      */
  293.     if (!any_segs && segment == first_seg) {
  294.     int tempint;               /* ignored */
  295.     if (segment != obj_segment("__NASMDEFSEG", 2, &tempint))
  296.         error (ERR_PANIC, "strange segment conditions in OBJ driver");
  297.     }
  298.  
  299.     for (seg = seghead; seg; seg = seg->next)
  300.     if (seg->index == segment) {
  301.         /*
  302.          * Case (ii). Maybe MODPUB someday?
  303.          */
  304.         if (is_global) {
  305.         struct Public *pub;
  306.         pub = *seg->pubtail = nasm_malloc(sizeof(*pub));
  307.         seg->pubtail = &pub->next;
  308.         pub->next = NULL;
  309.         pub->name = name;
  310.         pub->offset = offset;
  311.         }
  312.         return;
  313.     }
  314.  
  315.     /*
  316.      * Case (iii).
  317.      */
  318.     ext = *exttail = nasm_malloc(sizeof(*ext));
  319.     ext->next = NULL;
  320.     exttail = &ext->next;
  321.     ext->name = name;
  322.     if (is_global == 2)
  323.     ext->commonsize = offset;
  324.     else
  325.     ext->commonsize = 0;
  326.  
  327.     i = segment/2;
  328.     eb = ebhead;
  329.     if (!eb) {
  330.     eb = *ebtail = nasm_malloc(sizeof(*eb));
  331.     eb->next = NULL;
  332.     ebtail = &eb->next;
  333.     }
  334.     while (i > EXT_BLKSIZ) {
  335.     if (eb && eb->next)
  336.         eb = eb->next;
  337.     else {
  338.         eb = *ebtail = nasm_malloc(sizeof(*eb));
  339.         eb->next = NULL;
  340.         ebtail = &eb->next;
  341.     }
  342.     i -= EXT_BLKSIZ;
  343.     }
  344.     eb->index[i] = ++externals;
  345. }
  346.  
  347. static void obj_out (long segto, void *data, unsigned long type,
  348.              long segment, long wrt) {
  349.     long size, realtype;
  350.     unsigned char *ucdata;
  351.     long ldata;
  352.     struct Segment *seg;
  353.  
  354.     /*
  355.      * handle absolute-assembly (structure definitions)
  356.      */
  357.     if (segto == NO_SEG) {
  358.     if ((type & OUT_TYPMASK) != OUT_RESERVE)
  359.         error (ERR_NONFATAL, "attempt to assemble code in [ABSOLUTE]"
  360.            " space");
  361.     return;
  362.     }
  363.  
  364.     /*
  365.      * If `any_segs' is still FALSE, we must define a default
  366.      * segment.
  367.      */
  368.     if (!any_segs) {
  369.     int tempint;               /* ignored */
  370.     if (segto != obj_segment("__NASMDEFSEG", 2, &tempint))
  371.         error (ERR_PANIC, "strange segment conditions in OBJ driver");
  372.     }
  373.  
  374.     /*
  375.      * Find the segment we are targetting.
  376.      */
  377.     for (seg = seghead; seg; seg = seg->next)
  378.     if (seg->index == segto)
  379.         break;
  380.     if (!seg)
  381.     error (ERR_PANIC, "code directed to nonexistent segment?");
  382.  
  383.     size = type & OUT_SIZMASK;
  384.     realtype = type & OUT_TYPMASK;
  385.     if (realtype == OUT_RAWDATA) {
  386.     ucdata = data;
  387.     while (size > 0) {
  388.         long len = obj_ledata_space(seg);
  389.         if (len == 0) {
  390.         obj_ledata_new(seg);
  391.         len = obj_ledata_space(seg);
  392.         }
  393.         if (len > size)
  394.         len = size;
  395.         datacurr->lptr = obj_write_data (datacurr->lptr, ucdata, len);
  396.         datacurr->nonempty = TRUE;
  397.         ucdata += len;
  398.         size -= len;
  399.         seg->currentpos += len;
  400.     }
  401.     } else if (realtype == OUT_ADDRESS || realtype == OUT_REL2ADR ||
  402.            realtype == OUT_REL4ADR) {
  403.     if (segment == NO_SEG && realtype != OUT_ADDRESS)
  404.         error(ERR_NONFATAL, "relative call to absolute address not"
  405.           " supported by OBJ format");
  406.     if (segment >= SEG_ABS)
  407.         error(ERR_NONFATAL, "far-absolute relocations not supported"
  408.           " by OBJ format");
  409.     ldata = *(long *)data;
  410.     if (realtype == OUT_REL2ADR)
  411.         ldata += (size-2);
  412.     if (realtype == OUT_REL4ADR)
  413.         ldata += (size-4);
  414.     if (obj_ledata_space(seg) < 4 || !obj_fixup_free(seg))
  415.         obj_ledata_new(seg);
  416.     if (size == 2)
  417.         datacurr->lptr = obj_write_word (datacurr->lptr, ldata);
  418.     else
  419.         datacurr->lptr = obj_write_dword (datacurr->lptr, ldata);
  420.     datacurr->nonempty = TRUE;
  421.     if (segment != NO_SEG)
  422.         obj_write_fixup (datacurr, size,
  423.                  (realtype == OUT_REL2ADR ||
  424.                   realtype == OUT_REL4ADR ? 0 : 0x4000),
  425.                  segment, wrt,
  426.                  (seg->currentpos - datacurr->startpos));
  427.     seg->currentpos += size;
  428.     } else if (realtype == OUT_RESERVE) {
  429.     obj_ledata_commit();
  430.     seg->currentpos += size;
  431.     }
  432. }
  433.  
  434. static long obj_ledata_space(struct Segment *segto) {
  435.     if (datacurr && datacurr->seg == segto)
  436.     return datacurr->ledata + LEDATA_MAX - datacurr->lptr;
  437.     else
  438.     return 0;
  439. }
  440.  
  441. static int obj_fixup_free(struct Segment *segto) {
  442.     if (datacurr && datacurr->seg == segto)
  443.     return (datacurr->fixupp + RECORD_MAX - datacurr->fptr) > 8;
  444.     else
  445.     return 0;
  446. }
  447.  
  448. static void obj_ledata_new(struct Segment *segto) {
  449.     datacurr = *datatail = nasm_malloc(sizeof(*datacurr));
  450.     datacurr->next = NULL;
  451.     datatail = &datacurr->next;
  452.     datacurr->nonempty = FALSE;
  453.     datacurr->lptr = datacurr->ledata;
  454.     datacurr->fptr = datacurr->fixupp;
  455.     datacurr->seg = segto;
  456.     if (segto->use32)
  457.     datacurr->letype = LEDATA+1;
  458.     else
  459.     datacurr->letype = LEDATA;
  460.     datacurr->startpos = segto->currentpos;
  461.     datacurr->ftype = FIXUPP;
  462.  
  463.     datacurr->lptr = obj_write_index (datacurr->lptr, segto->obj_index);
  464.     if (datacurr->letype == LEDATA)
  465.     datacurr->lptr = obj_write_word (datacurr->lptr, segto->currentpos);
  466.     else
  467.     datacurr->lptr = obj_write_dword (datacurr->lptr, segto->currentpos);
  468. }
  469.  
  470. static void obj_ledata_commit(void) {
  471.     datacurr = NULL;
  472. }
  473.  
  474. static void obj_write_fixup (struct ObjData *data, int bytes,
  475.                  int segrel, long seg, long wrt,
  476.                  long offset) {
  477.     int locat, method;
  478.     int base;
  479.     long tidx, fidx;
  480.     struct Segment *s = NULL;
  481.     struct Group *g = NULL;
  482.  
  483.     locat = 0x8000 | segrel | offset;
  484.     if (seg % 2) {
  485.     base = TRUE;
  486.     locat |= 0x800;
  487.     seg--;
  488.     if (bytes != 2)
  489.         error(ERR_NONFATAL, "OBJ format can only handle 2-byte"
  490.           " segment base references");
  491.     } else {
  492.     base = FALSE;
  493.     if (bytes == 2)
  494.         locat |= 0x400;
  495.     else {
  496.         locat |= 0x2400;
  497.         data->ftype = FIXUPP+1;    /* need new-style FIXUPP record */
  498.     }
  499.     }
  500.     data->fptr = obj_write_rword (data->fptr, locat);
  501.  
  502.     tidx = fidx = -1, method = 0;      /* placate optimisers */
  503.  
  504.     /*
  505.      * See if we can find the segment ID in our segment list. If
  506.      * so, we have a T4 (LSEG) target.
  507.      */
  508.     for (s = seghead; s; s = s->next)
  509.     if (s->index == seg)
  510.         break;
  511.     if (s)
  512.     method = 4, tidx = s->obj_index;
  513.     else {
  514.     for (g = grphead; g; g = g->next)
  515.         if (g->index == seg)
  516.         break;
  517.     if (g)
  518.         method = 5, tidx = g->obj_index;
  519.     else {
  520.         long i = seg/2;
  521.         struct ExtBack *eb = ebhead;
  522.         while (i > EXT_BLKSIZ) {
  523.         if (eb)
  524.             eb = eb->next;
  525.         else
  526.             break;
  527.         i -= EXT_BLKSIZ;
  528.         }
  529.         if (eb)
  530.         method = 6, tidx = eb->index[i];
  531.         else
  532.         error(ERR_PANIC,
  533.               "unrecognised segment value in obj_write_fixup");
  534.     }
  535.     }
  536.  
  537.     /*
  538.      * If no WRT given, assume the natural default, which is method
  539.      * F5 unless we are doing an OFFSET fixup for a grouped
  540.      * segment, in which case we require F1 (group). Oh, and in
  541.      * OS/2 mode we're in F1 (group) on `defgrp' _always_, by
  542.      * default.
  543.      */
  544.     if (wrt == NO_SEG) {
  545.     if (os2)
  546.         method |= 0x10, fidx = defgrp->obj_index;
  547.     else if (!base && s && s->grp)
  548.         method |= 0x10, fidx = s->grp->obj_index;
  549.     else
  550.         method |= 0x50, fidx = -1;
  551.     } else {
  552.     /*
  553.      * See if we can find the WRT-segment ID in our segment
  554.      * list. If so, we have a F0 (LSEG) frame.
  555.      */
  556.     for (s = seghead; s; s = s->next)
  557.         if (s->index == wrt-1)
  558.         break;
  559.     if (s)
  560.         method |= 0x00, fidx = s->obj_index;
  561.     else {
  562.         for (g = grphead; g; g = g->next)
  563.         if (g->index == wrt-1)
  564.             break;
  565.         if (g)
  566.         method |= 0x10, fidx = g->obj_index;
  567.         else {
  568.         long i = wrt/2;
  569.         struct ExtBack *eb = ebhead;
  570.         while (i > EXT_BLKSIZ) {
  571.             if (eb)
  572.             eb = eb->next;
  573.             else
  574.             break;
  575.             i -= EXT_BLKSIZ;
  576.         }
  577.         if (eb)
  578.             method |= 0x20, fidx = eb->index[i];
  579.         else
  580.             error(ERR_PANIC,
  581.               "unrecognised WRT value in obj_write_fixup");
  582.         }
  583.     }
  584.     }
  585.  
  586.     data->fptr = obj_write_byte (data->fptr, method);
  587.     if (fidx != -1)
  588.     data->fptr = obj_write_index (data->fptr, fidx);
  589.     data->fptr = obj_write_index (data->fptr, tidx);
  590. }
  591.  
  592. static long obj_segment (char *name, int pass, int *bits) {
  593.     /*
  594.      * We call the label manager here to define a name for the new
  595.      * segment, and when our _own_ label-definition stub gets
  596.      * called in return, it should register the new segment name
  597.      * using the pointer it gets passed. That way we save memory,
  598.      * by sponging off the label manager.
  599.      */
  600.     if (!name) {
  601.     *bits = 16;
  602.     return first_seg;
  603.     } else {
  604.     struct Segment *seg;
  605.     struct Group *grp;
  606.     int obj_idx, i, attrs, rn_error;
  607.     char *p;
  608.  
  609.     /*
  610.      * Look for segment attributes.
  611.      */
  612.     attrs = 0;
  613.     while (*name == '.')
  614.         name++;               /* hack, but a documented one */
  615.     p = name;
  616.     while (*p && !isspace(*p))
  617.         p++;
  618.     if (*p) {
  619.         *p++ = '\0';
  620.         while (*p && isspace(*p))
  621.         *p++ = '\0';
  622.     }
  623.     while (*p) {
  624.         while (*p && !isspace(*p))
  625.         p++;
  626.         if (*p) {
  627.         *p++ = '\0';
  628.         while (*p && isspace(*p))
  629.             *p++ = '\0';
  630.         }
  631.  
  632.         attrs++;
  633.     }
  634.  
  635.     obj_idx = 1;
  636.     for (seg = seghead; seg; seg = seg->next) {
  637.         obj_idx++;
  638.         if (!strcmp(seg->name, name)) {
  639.         if (attrs > 0 && pass == 1)
  640.             error(ERR_WARNING, "segment attributes specified on"
  641.               " redeclaration of segment: ignoring");
  642.         if (seg->use32)
  643.             *bits = 32;
  644.         else
  645.             *bits = 16;
  646.         return seg->index;
  647.         }
  648.     }
  649.  
  650.     *segtail = seg = nasm_malloc(sizeof(*seg));
  651.     seg->next = NULL;
  652.     segtail = &seg->next;
  653.     seg->index = (any_segs ? seg_alloc() : first_seg);
  654.     seg->obj_index = obj_idx;
  655.     seg->grp = NULL;
  656.     any_segs = TRUE;
  657.     seg->name = NULL;
  658.     seg->currentpos = 0;
  659.     seg->align = 1;               /* default */
  660.     seg->use32 = FALSE;           /* default */
  661.     seg->combine = CMB_PUBLIC;     /* default */
  662.     seg->segclass = seg->overlay = NULL;
  663.     seg->pubhead = NULL;
  664.     seg->pubtail = &seg->pubhead;
  665.  
  666.     /*
  667.      * Process the segment attributes.
  668.      */
  669.     p = name;
  670.     while (attrs--) {
  671.         p += strlen(p);
  672.         while (!*p) p++;
  673.  
  674.         /*
  675.          * `p' contains a segment attribute.
  676.          */
  677.         if (!nasm_stricmp(p, "private"))
  678.         seg->combine = CMB_PRIVATE;
  679.         else if (!nasm_stricmp(p, "public"))
  680.         seg->combine = CMB_PUBLIC;
  681.         else if (!nasm_stricmp(p, "common"))
  682.         seg->combine = CMB_COMMON;
  683.         else if (!nasm_stricmp(p, "stack"))
  684.         seg->combine = CMB_STACK;
  685.         else if (!nasm_stricmp(p, "use16"))
  686.         seg->use32 = FALSE;
  687.         else if (!nasm_stricmp(p, "use32"))
  688.         seg->use32 = TRUE;
  689.         else if (!nasm_strnicmp(p, "class=", 6))
  690.         seg->segclass = nasm_strdup(p+6);
  691.         else if (!nasm_strnicmp(p, "overlay=", 8))
  692.         seg->overlay = nasm_strdup(p+8);
  693.         else if (!nasm_strnicmp(p, "align=", 6)) {
  694.         seg->align = readnum(p+6, &rn_error);
  695.         if (rn_error) {
  696.             seg->align = 1;
  697.             error (ERR_NONFATAL, "segment alignment should be"
  698.                " numeric");
  699.         }
  700.         switch ((int) seg->align) {
  701.           case 1:           /* BYTE */
  702.           case 2:           /* WORD */
  703.           case 4:           /* DWORD */
  704.           case 16:           /* PARA */
  705.           case 256:           /* PAGE */
  706.             break;
  707.           case 8:
  708.             error(ERR_WARNING, "OBJ format does not support alignment"
  709.               " of 8: rounding up to 16");
  710.             seg->align = 16;
  711.             break;
  712.           case 32:
  713.           case 64:
  714.           case 128:
  715.             error(ERR_WARNING, "OBJ format does not support alignment"
  716.               " of %d: rounding up to 256", seg->align);
  717.             seg->align = 256;
  718.             break;
  719.           default:
  720.             error(ERR_NONFATAL, "invalid alignment value %d",
  721.               seg->align);
  722.             seg->align = 1;
  723.             break;
  724.         }
  725.         } else if (!nasm_strnicmp(p, "absolute=", 9)) {
  726.         seg->align = SEG_ABS + readnum(p+9, &rn_error);
  727.         if (rn_error)
  728.             error (ERR_NONFATAL, "argument to `absolute' segment"
  729.                " attribute should be numeric");
  730.         }
  731.     }
  732.  
  733.     obj_seg_needs_update = seg;
  734.     if (seg->align >= SEG_ABS)
  735.         deflabel (name, NO_SEG, seg->align - SEG_ABS, &of_obj, error);
  736.     else
  737.         deflabel (name, seg->index+1, 0L, &of_obj, error);
  738.     obj_seg_needs_update = NULL;
  739.  
  740.     /*
  741.      * See if this segment is defined in any groups.
  742.      */
  743.     for (grp = grphead; grp; grp = grp->next) {
  744.         for (i = grp->nindices; i < grp->nentries; i++) {
  745.         if (!strcmp(grp->segs[i].name, seg->name)) {
  746.             nasm_free (grp->segs[i].name);
  747.             grp->segs[i] = grp->segs[grp->nindices];
  748.             grp->segs[grp->nindices++].index = seg->obj_index;
  749.             if (seg->grp)
  750.             error(ERR_WARNING, "segment `%s' is already part of"
  751.                   " a group: first one takes precedence",
  752.                   seg->name);
  753.             else
  754.             seg->grp = grp;
  755.         }
  756.         }
  757.     }
  758.  
  759.     if (seg->use32)
  760.         *bits = 32;
  761.     else
  762.         *bits = 16;
  763.     return seg->index;
  764.     }
  765. }
  766.  
  767. static int obj_directive (char *directive, char *value, int pass) {
  768.     if (!strcmp(directive, "group")) {
  769.     char *p, *q, *v;
  770.     if (pass == 1) {
  771.         struct Group *grp;
  772.         struct Segment *seg;
  773.         int obj_idx;
  774.  
  775.         q = value;
  776.         while (*q == '.')
  777.         q++;               /* hack, but a documented one */
  778.         v = q;
  779.         while (*q && !isspace(*q))
  780.         q++;
  781.         if (isspace(*q)) {
  782.         *q++ = '\0';
  783.         while (*q && isspace(*q))
  784.             q++;
  785.         }
  786.         /*
  787.          * Here we used to sanity-check the group directive to
  788.          * ensure nobody tried to declare a group containing no
  789.          * segments. However, OS/2 does this as standard
  790.          * practice, so the sanity check has been removed.
  791.          *
  792.          * if (!*q) {
  793.          *     error(ERR_NONFATAL,"GROUP directive contains no segments");
  794.          *     return 1;
  795.          * }
  796.          */
  797.  
  798.         obj_idx = 1;
  799.         for (grp = grphead; grp; grp = grp->next) {
  800.         obj_idx++;
  801.         if (!strcmp(grp->name, v)) {
  802.             error(ERR_NONFATAL, "group `%s' defined twice", v);
  803.             return 1;
  804.         }
  805.         }
  806.  
  807.         *grptail = grp = nasm_malloc(sizeof(*grp));
  808.         grp->next = NULL;
  809.         grptail = &grp->next;
  810.         grp->index = seg_alloc();
  811.         grp->obj_index = obj_idx;
  812.         grp->nindices = grp->nentries = 0;
  813.         grp->name = NULL;
  814.  
  815.         obj_grp_needs_update = grp;
  816.         deflabel (v, grp->index+1, 0L, &of_obj, error);
  817.         obj_grp_needs_update = NULL;
  818.  
  819.         while (*q) {
  820.         p = q;
  821.         while (*q && !isspace(*q))
  822.             q++;
  823.         if (isspace(*q)) {
  824.             *q++ = '\0';
  825.             while (*q && isspace(*q))
  826.             q++;
  827.         }
  828.         /*
  829.          * Now p contains a segment name. Find it.
  830.          */
  831.         for (seg = seghead; seg; seg = seg->next)
  832.             if (!strcmp(seg->name, p))
  833.             break;
  834.         if (seg) {
  835.             /*
  836.              * We have a segment index. Shift a name entry
  837.              * to the end of the array to make room.
  838.              */
  839.             grp->segs[grp->nentries++] = grp->segs[grp->nindices];
  840.             grp->segs[grp->nindices++].index = seg->obj_index;
  841.             if (seg->grp)
  842.             error(ERR_WARNING, "segment `%s' is already part of"
  843.                   " a group: first one takes precedence",
  844.                   seg->name);
  845.             else
  846.             seg->grp = grp;
  847.         } else {
  848.             /*
  849.              * We have an as-yet undefined segment.
  850.              * Remember its name, for later.
  851.              */
  852.             grp->segs[grp->nentries++].name = nasm_strdup(p);
  853.         }
  854.         }
  855.     }
  856.     return 1;
  857.     }
  858.     if (!strcmp(directive, "uppercase")) {
  859.     obj_uppercase = TRUE;
  860.     return 1;
  861.     }
  862.     return 0;
  863. }
  864.  
  865. static long obj_segbase (long segment) {
  866.     struct Segment *seg;
  867.  
  868.     /*
  869.      * Find the segment in our list.
  870.      */
  871.     for (seg = seghead; seg; seg = seg->next)
  872.     if (seg->index == segment-1)
  873.         break;
  874.  
  875.     if (!seg)
  876.     return segment;               /* not one of ours - leave it alone */
  877.  
  878.     if (seg->align >= SEG_ABS)
  879.     return seg->align;           /* absolute segment */
  880.     if (seg->grp)
  881.     return seg->grp->index+1;      /* grouped segment */
  882.  
  883.     return segment;               /* no special treatment */
  884. }
  885.  
  886. static void obj_filename (char *inname, char *outname, efunc error) {
  887.     strcpy(obj_infile, inname);
  888.     standard_extension (inname, outname, ".obj", error);
  889. }
  890.  
  891. static void obj_write_file (void) {
  892.     struct Segment *seg;
  893.     struct Group *grp;
  894.     struct Public *pub;
  895.     struct External *ext;
  896.     struct ObjData *data;
  897.     static char boast[] = "The Netwide Assembler " NASM_VER;
  898.     int lname_idx, rectype;
  899.  
  900.     /*
  901.      * Write the THEADR module header.
  902.      */
  903.     recptr = record;
  904.     recptr = obj_write_name (recptr, obj_infile);
  905.     obj_record (THEADR, record, recptr);
  906.  
  907.     /*
  908.      * Write the NASM boast comment.
  909.      */
  910.     recptr = record;
  911.     recptr = obj_write_rword (recptr, 0);   /* comment type zero */
  912.     recptr = obj_write_name (recptr, boast);
  913.     obj_record (COMENT, record, recptr);
  914.  
  915.     /*
  916.      * Write the first LNAMES record, containing LNAME one, which
  917.      * is null. Also initialise the LNAME counter.
  918.      */
  919.     recptr = record;
  920.     recptr = obj_write_name (recptr, "");
  921.     obj_record (LNAMES, record, recptr);
  922.     lname_idx = 2;
  923.  
  924.     /*
  925.      * Write the SEGDEF records. Each has an associated LNAMES
  926.      * record.
  927.      */
  928.     for (seg = seghead; seg; seg = seg->next) {
  929.     int new_segdef;               /* do we use the newer record type? */
  930.     int acbp;
  931.     int sn, cn, on;               /* seg, class, overlay LNAME idx */
  932.  
  933.     if (seg->use32 || seg->currentpos >= 0x10000L)
  934.         new_segdef = TRUE;
  935.     else
  936.         new_segdef = FALSE;
  937.  
  938.     recptr = record;
  939.     recptr = obj_write_name (recptr, seg->name);
  940.     sn = lname_idx++;
  941.     if (seg->segclass) {
  942.         recptr = obj_write_name (recptr, seg->segclass);
  943.         cn = lname_idx++;
  944.     } else
  945.         cn = 1;
  946.     if (seg->overlay) {
  947.         recptr = obj_write_name (recptr, seg->overlay);
  948.         on = lname_idx++;
  949.     } else
  950.         on = 1;
  951.     obj_record (LNAMES, record, recptr);
  952.  
  953.     acbp = (seg->combine << 2);    /* C field */
  954.  
  955.     if (seg->currentpos >= 0x10000L && !new_segdef)
  956.         acbp |= 0x02;           /* B bit */
  957.  
  958.     if (seg->use32)
  959.         acbp |= 0x01;           /* P bit is Use32 flag */
  960.  
  961.     /* A field */
  962.     if (seg->align >= SEG_ABS)
  963.         acbp |= 0x00;
  964.     else if (seg->align >= 256) {
  965.         if (seg->align > 256)
  966.         error(ERR_NONFATAL, "segment `%s' requires more alignment"
  967.               " than OBJ format supports", seg->name);
  968.         acbp |= 0x80;
  969.     } else if (seg->align >= 16) {
  970.         acbp |= 0x60;
  971.     } else if (seg->align >= 4) {
  972.         acbp |= 0xA0;
  973.     } else if (seg->align >= 2) {
  974.         acbp |= 0x40;
  975.     } else
  976.         acbp |= 0x20;
  977.  
  978.     recptr = record;
  979.     recptr = obj_write_byte (recptr, acbp);
  980.     if (seg->align & SEG_ABS) {
  981.         recptr = obj_write_word (recptr, seg->align - SEG_ABS);
  982.         recptr = obj_write_byte (recptr, 0);
  983.     }
  984.     if (new_segdef)
  985.         recptr = obj_write_dword (recptr, seg->currentpos);
  986.     else
  987.         recptr = obj_write_word (recptr, seg->currentpos & 0xFFFF);
  988.     recptr = obj_write_index (recptr, sn);
  989.     recptr = obj_write_index (recptr, cn);
  990.     recptr = obj_write_index (recptr, on);
  991.     if (new_segdef)
  992.         obj_record (SEGDEF+1, record, recptr);
  993.     else
  994.         obj_record (SEGDEF, record, recptr);
  995.     }
  996.  
  997.     /*
  998.      * Write some LNAMES for the group names. lname_idx is left
  999.      * alone here - it will catch up when we write the GRPDEFs.
  1000.      */
  1001.     recptr = record;
  1002.     for (grp = grphead; grp; grp = grp->next) {
  1003.     recptr = obj_write_name (recptr, grp->name);
  1004.     if (recptr - record > 1024) {
  1005.         obj_record (LNAMES, record, recptr);
  1006.         recptr = record;
  1007.     }
  1008.     }
  1009.     if (recptr > record)
  1010.     obj_record (LNAMES, record, recptr);
  1011.  
  1012.     /*
  1013.      * Write the GRPDEF records.
  1014.      */
  1015.     for (grp = grphead; grp; grp = grp->next) {
  1016.     int i;
  1017.  
  1018.     if (grp->nindices != grp->nentries) {
  1019.         for (i = grp->nindices; i < grp->nentries; i++) {
  1020.         error(ERR_NONFATAL, "group `%s' contains undefined segment"
  1021.               " `%s'", grp->name, grp->segs[i].name);
  1022.         nasm_free (grp->segs[i].name);
  1023.         grp->segs[i].name = NULL;
  1024.         }
  1025.     }
  1026.     recptr = record;
  1027.     recptr = obj_write_index (recptr, lname_idx++);
  1028.     for (i = 0; i < grp->nindices; i++) {
  1029.         recptr = obj_write_byte (recptr, 0xFF);
  1030.         recptr = obj_write_index (recptr, grp->segs[i].index);
  1031.     }
  1032.     obj_record (GRPDEF, record, recptr);
  1033.     }
  1034.  
  1035.     /*
  1036.      * Write the PUBDEF records: first the ones in the segments,
  1037.      * then the far-absolutes.
  1038.      */
  1039.     for (seg = seghead; seg; seg = seg->next) {
  1040.     int any;
  1041.  
  1042.     recptr = record;
  1043.     recptr = obj_write_index (recptr, seg->grp ? seg->grp->obj_index : 0);
  1044.     recptr = obj_write_index (recptr, seg->obj_index);
  1045.     any = FALSE;
  1046.     if (seg->use32)
  1047.         rectype = PUBDEF+1;
  1048.     else
  1049.         rectype = PUBDEF;
  1050.     for (pub = seg->pubhead; pub; pub = pub->next) {
  1051.         if (recptr - record + strlen(pub->name) > 1024) {
  1052.         if (any)
  1053.             obj_record (rectype, record, recptr);
  1054.         recptr = record;
  1055.         recptr = obj_write_index (recptr, 0);
  1056.         recptr = obj_write_index (recptr, seg->obj_index);
  1057.         }
  1058.         recptr = obj_write_name (recptr, pub->name);
  1059.         if (seg->use32)
  1060.         recptr = obj_write_dword (recptr, pub->offset);
  1061.         else
  1062.         recptr = obj_write_word (recptr, pub->offset);
  1063.         recptr = obj_write_index (recptr, 0);
  1064.         any = TRUE;
  1065.     }
  1066.     if (any)
  1067.         obj_record (rectype, record, recptr);
  1068.     }
  1069.     for (pub = fpubhead; pub; pub = pub->next) {   /* pub-crawl :-) */
  1070.     recptr = record;
  1071.     recptr = obj_write_index (recptr, 0);   /* no group */
  1072.     recptr = obj_write_index (recptr, 0);   /* no segment either */
  1073.     recptr = obj_write_word (recptr, pub->segment);
  1074.     recptr = obj_write_name (recptr, pub->name);
  1075.     recptr = obj_write_word (recptr, pub->offset);
  1076.     recptr = obj_write_index (recptr, 0);
  1077.     obj_record (PUBDEF, record, recptr);
  1078.     }
  1079.  
  1080.     /*
  1081.      * Write the EXTDEF and COMDEF records, in order.
  1082.      */
  1083.     recptr = record;
  1084.     for (ext = exthead; ext; ext = ext->next) {
  1085.     if (ext->commonsize == 0) {
  1086.         recptr = obj_write_name (recptr, ext->name);
  1087.         recptr = obj_write_index (recptr, 0);
  1088.         if (recptr - record > 1024) {
  1089.         obj_record (EXTDEF, record, recptr);
  1090.         recptr = record;
  1091.         }
  1092.     } else {
  1093.         if (recptr > record)
  1094.         obj_record (EXTDEF, record, recptr);
  1095.         recptr = record;
  1096.         if (ext->commonsize > 0) {
  1097.         recptr = obj_write_name (recptr, ext->name);
  1098.         recptr = obj_write_index (recptr, 0);
  1099.         recptr = obj_write_byte (recptr, 0x61);/* far communal */
  1100.         recptr = obj_write_value (recptr, 1L);
  1101.         recptr = obj_write_value (recptr, ext->commonsize);
  1102.         obj_record (COMDEF, record, recptr);
  1103.         } else if (ext->commonsize < 0) {
  1104.         recptr = obj_write_name (recptr, ext->name);
  1105.         recptr = obj_write_index (recptr, 0);
  1106.         recptr = obj_write_byte (recptr, 0x62);/* near communal */
  1107.         recptr = obj_write_value (recptr, ext->commonsize);
  1108.         obj_record (COMDEF, record, recptr);
  1109.         }
  1110.         recptr = record;
  1111.     }
  1112.     }
  1113.     if (recptr > record)
  1114.     obj_record (EXTDEF, record, recptr);
  1115.  
  1116.     /*
  1117.      * Write a COMENT record stating that the linker's first pass
  1118.      * may stop processing at this point. Exception is if we're in
  1119.      * OS/2 mode and our MODEND record specifies a start point, in
  1120.      * which case, according to the OS/2 documentation, this COMENT
  1121.      * should be omitted.
  1122.      */
  1123.     if (!os2 || obj_entry_seg == NO_SEG) {
  1124.     recptr = record;
  1125.     recptr = obj_write_rword (recptr, 0x40A2);
  1126.     recptr = obj_write_byte (recptr, 1);
  1127.     obj_record (COMENT, record, recptr);
  1128.     }
  1129.  
  1130.     /*
  1131.      * Write the LEDATA/FIXUPP pairs.
  1132.      */
  1133.     for (data = datahead; data; data = data->next) {
  1134.     if (data->nonempty) {
  1135.         obj_record (data->letype, data->ledata, data->lptr);
  1136.         if (data->fptr != data->fixupp)
  1137.         obj_record (data->ftype, data->fixupp, data->fptr);
  1138.     }
  1139.     }
  1140.  
  1141.     /*
  1142.      * Write the MODEND module end marker.
  1143.      */
  1144.     recptr = record;
  1145.     rectype = MODEND;
  1146.     if (obj_entry_seg != NO_SEG) {
  1147.     recptr = obj_write_byte (recptr, 0xC1);
  1148.     /*
  1149.      * Find the segment in the segment list.
  1150.      */
  1151.     for (seg = seghead; seg; seg = seg->next) {
  1152.         if (seg->index == obj_entry_seg) {
  1153.         if (seg->grp) {
  1154.             recptr = obj_write_byte (recptr, 0x10);
  1155.             recptr = obj_write_index (recptr, seg->grp->obj_index);
  1156.         } else {
  1157.             recptr = obj_write_byte (recptr, 0x50);
  1158.         }
  1159.         recptr = obj_write_index (recptr, seg->obj_index);
  1160.         if (seg->use32) {
  1161.             rectype = MODEND+1;
  1162.             recptr = obj_write_dword (recptr, obj_entry_ofs);
  1163.         } else
  1164.             recptr = obj_write_word (recptr, obj_entry_ofs);
  1165.         break;
  1166.         }
  1167.     }
  1168.     if (!seg)
  1169.         error(ERR_NONFATAL, "entry point is not in this module");
  1170.     } else
  1171.     recptr = obj_write_byte (recptr, 0);
  1172.     obj_record (rectype, record, recptr);
  1173. }
  1174.  
  1175. static unsigned char *obj_write_data(unsigned char *ptr,
  1176.                      unsigned char *data, int len) {
  1177.     while (len--)
  1178.     *ptr++ = *data++;
  1179.     return ptr;
  1180. }
  1181.  
  1182. static unsigned char *obj_write_byte(unsigned char *ptr, int data) {
  1183.     *ptr++ = data;
  1184.     return ptr;
  1185. }
  1186.  
  1187. static unsigned char *obj_write_word(unsigned char *ptr, int data) {
  1188.     *ptr++ = data & 0xFF;
  1189.     *ptr++ = (data >> 8) & 0xFF;
  1190.     return ptr;
  1191. }
  1192.  
  1193. static unsigned char *obj_write_dword(unsigned char *ptr, long data) {
  1194.     *ptr++ = data & 0xFF;
  1195.     *ptr++ = (data >> 8) & 0xFF;
  1196.     *ptr++ = (data >> 16) & 0xFF;
  1197.     *ptr++ = (data >> 24) & 0xFF;
  1198.     return ptr;
  1199. }
  1200.  
  1201. static unsigned char *obj_write_rword(unsigned char *ptr, int data) {
  1202.     *ptr++ = (data >> 8) & 0xFF;
  1203.     *ptr++ = data & 0xFF;
  1204.     return ptr;
  1205. }
  1206.  
  1207. static unsigned char *obj_write_name(unsigned char *ptr, char *data) {
  1208.     *ptr++ = strlen(data);
  1209.     if (obj_uppercase) {
  1210.     while (*data) {
  1211.         *ptr++ = (unsigned char) toupper(*data);
  1212.         data++;
  1213.     }
  1214.     } else {
  1215.     while (*data)
  1216.         *ptr++ = (unsigned char) *data++;
  1217.     }
  1218.     return ptr;
  1219. }
  1220.  
  1221. static unsigned char *obj_write_index(unsigned char *ptr, int data) {
  1222.     if (data < 128)
  1223.     *ptr++ = data;
  1224.     else {
  1225.     *ptr++ = 0x80 | ((data >> 8) & 0x7F);
  1226.     *ptr++ = data & 0xFF;
  1227.     }
  1228.     return ptr;
  1229. }
  1230.  
  1231. static unsigned char *obj_write_value(unsigned char *ptr,
  1232.                       unsigned long data) {
  1233.     if (data <= 128)
  1234.     *ptr++ = data;
  1235.     else if (data <= 0xFFFF) {
  1236.     *ptr++ = 129;
  1237.     *ptr++ = data & 0xFF;
  1238.     *ptr++ = (data >> 8) & 0xFF;
  1239.     } else if (data <= 0xFFFFFFL) {
  1240.     *ptr++ = 132;
  1241.     *ptr++ = data & 0xFF;
  1242.     *ptr++ = (data >> 8) & 0xFF;
  1243.     *ptr++ = (data >> 16) & 0xFF;
  1244.     } else {
  1245.     *ptr++ = 136;
  1246.     *ptr++ = data & 0xFF;
  1247.     *ptr++ = (data >> 8) & 0xFF;
  1248.     *ptr++ = (data >> 16) & 0xFF;
  1249.     *ptr++ = (data >> 24) & 0xFF;
  1250.     }
  1251.     return ptr;
  1252. }
  1253.  
  1254. static void obj_record(int type, unsigned char *start, unsigned char *end) {
  1255.     unsigned long cksum, len;
  1256.  
  1257.     cksum = type;
  1258.     fputc (type, ofp);
  1259.     len = end-start+1;
  1260.     cksum += (len & 0xFF) + ((len>>8) & 0xFF);
  1261.     fwriteshort (len, ofp);
  1262.     fwrite (start, 1, end-start, ofp);
  1263.     while (start < end)
  1264.     cksum += *start++;
  1265.     fputc ( (-cksum) & 0xFF, ofp);
  1266. }
  1267.  
  1268. struct ofmt of_obj = {
  1269.     "Microsoft MS-DOS 16-bit OMF object files",
  1270.     "obj",
  1271.     dos_init,
  1272.     obj_out,
  1273.     obj_deflabel,
  1274.     obj_segment,
  1275.     obj_segbase,
  1276.     obj_directive,
  1277.     obj_filename,
  1278.     obj_cleanup
  1279. };
  1280.  
  1281. struct ofmt of_os2 = {
  1282.     "OS/2 object files (variant of OMF)",
  1283.     "os2",
  1284.     os2_init,
  1285.     obj_out,
  1286.     obj_deflabel,
  1287.     obj_segment,
  1288.     obj_segbase,
  1289.     obj_directive,
  1290.     obj_filename,
  1291.     obj_cleanup
  1292. };
  1293.  
  1294. #endif /* OF_OBJ */
  1295.